using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.ApplicationModel.ConversationalAgent;
using Windows.Media;
using Windows.Media.Audio;
using Windows.Media.Capture;
using Windows.Media.MediaProperties;
using Windows.Media.Render;
using Windows.Storage.Streams;
using Debug = Audio.Diagnostics.Debug;

namespace Audio
{
    internal class AudioHandler
    {
        private Queue<byte> m_byteListQueue = null;
        private readonly object m_bufferLock = new object();

        private int graphIndex = 0;
        private bool isRunning = false;

        private AudioGraph m_graph = null;
        private AudioDeviceInputNode m_inNode = null;
        private AudioFrameOutputNode m_frameOutNode = null;

        public virtual void CreatListQueue()
        {
            m_byteListQueue = new Queue<byte>();
        }
        public async Task<bool> StartAudioAsync()
        {
            Debug.WriteLine($"AudioHandler.StartAudioAsync");

            if (m_graph != null)
            {
                Dispose();
            }

            m_graph = await CreateAudioGraphAsync();
            if (m_graph == null)
            {
                Debug.WriteLine("AudioHandler.CreateAudioGraphAsync Error.");
                return false;
            }
            Debug.WriteLine("AudioHandler.CreateAudioGraphAsync OK.");
            graphIndex++;

            m_inNode = await CreateInputNodeAsync(m_graph);
            if(m_inNode == null)
            {
                Debug.WriteLine($"AudioHandler.CreateInputNodeAsync fail.");
                return false;
            }
            Debug.WriteLine("AudioHandler.CreateInputNodeAsync OK.");
            m_frameOutNode = CreateFrameOutputNode();
            if(m_frameOutNode == null)
            {
                Debug.WriteLine($"AudioHandler.CreateFrameOutputNode fail.");
                return false;
            }
            Debug.WriteLine("AudioHandler.CreateFrameOutputNode OK.");

            m_inNode.AddOutgoingConnection(m_frameOutNode);
            m_graph.QuantumStarted += OnQuantumStarted;
            this.CreatListQueue();
            m_graph.Start();
            isRunning = true;
            Debug.WriteLine($"AudioHandler.m_graph.Start");
            return true;
        }

        public void Dispose()
        {
            if (isRunning)
            {
                Debug.WriteLine("AudioHandler.Dispose, m_graph.Stop Enter");
                m_graph.Stop();
                Debug.WriteLine("AudioHandler.Dispose, m_graph.Stop End");
                m_graph.QuantumStarted -= OnQuantumStarted;
                isRunning = false;
            }

            if (m_frameOutNode != null)
            {
                Debug.WriteLine("AudioHandler.Dispose: m_frameOutNode.Dispose Enter");
                m_frameOutNode.Dispose();
                Debug.WriteLine("AudioHandler.Dispose: m_frameOutNode.Dispose End");
                m_frameOutNode = null;
            }

            if (m_inNode != null)
            {
                Debug.WriteLine("AudioHandler.Dispose: m_inNode.Dispose Enter");
                m_inNode.Dispose();
                Debug.WriteLine("AudioHandler.Dispose: m_inNode.Dispose End");
                m_inNode = null;
            }

            if (m_graph != null)
            {
                Debug.WriteLine("AudioHandler.Dispose: m_graph.Dispose Enter");
                m_graph.Dispose();
                Debug.WriteLine("AudioHandler.Dispose: m_graph.Dispose End");
                m_graph = null;
                graphIndex--;
            }
        }

        //private readonly object locker = new object();
        public void StopAudio()
        {
            Debug.WriteLine("AudioHandler.StopAudio, Enter");

            Dispose();

            Debug.WriteLine($"AudioHandler.StopAudio, Exit, graphIndex={graphIndex}");
        }

        public virtual void ProcessPcmData(Int16 intValue)
        {
            var bytes = BitConverter.GetBytes(intValue);
            foreach (var convertedByte in bytes)
            {
                m_byteListQueue.Enqueue(convertedByte);
            }
        }

        public const int PCM_BUFFER_SIZE = 5000;
        public static byte[] PcmBuffer = new byte[PCM_BUFFER_SIZE];
        public virtual bool FlushToPcmBuffer()
        {
            if (m_byteListQueue != null)
            {
                //Debug.WriteLine("lock wait:FlushToPcmBuffe");
                lock (m_bufferLock)
                {
                    //Debug.WriteLine("lock enter:FlushToPcmBuffe");
                    if (m_byteListQueue.Count >= PCM_BUFFER_SIZE)
                    {
                        for (int i = 0; i < PCM_BUFFER_SIZE; i++)
                        {
                            PcmBuffer[i] = m_byteListQueue.Dequeue();
                        }

                        return true;
                    }
                }
            }

            return false;
        }

        private void OnQuantumStarted(AudioGraph sender, object args)
        {
            if(m_frameOutNode != null)
            {
                ProcessFrameOutput(m_frameOutNode.GetFrame());
            }
        }

        private void ProcessFrameOutput(AudioFrame frame)
        {
            var audio = frame.LockBuffer(AudioBufferAccessMode.Read);
            var buffer = Windows.Storage.Streams.Buffer.CreateCopyFromMemoryBuffer(audio);
            buffer.Length = audio.Length;

            using (var reader = DataReader.FromBuffer(buffer))
            {
                reader.ByteOrder = ByteOrder.LittleEndian;

                //Debug.WriteLine("lock wait:ProcessFrameOutput");
                lock (m_bufferLock)
                {
                    //Debug.WriteLine("lock enter:ProcessFrameOutput");
                    while (reader.UnconsumedBufferLength > 0)
                    {
                        var singleTmp = reader.ReadSingle();
                        // AudioGraph data is retrieved in 32-bit floats; clamp this for conversion to 16-bit PCM
                        singleTmp = singleTmp > 1.0f ? 1.0f : singleTmp;
                        singleTmp = singleTmp < -1.0f ? -1.0f : singleTmp;

                        var intValue = (Int16)(singleTmp * Int16.MaxValue);

                        this.ProcessPcmData(intValue);

                      
                    }
                }
            }
        }

        private async Task<AudioGraph> CreateAudioGraphAsync()
        {
            AudioGraphSettings settings = new AudioGraphSettings(AudioRenderCategory.Speech);

            try
            {
                var result = await AudioGraph.CreateAsync(settings);
                Debug.WriteLine($"AudioHandler.AudioGraph.CreateAsync, status:{result.Status},errorCode:{result.ExtendedError}");
                if (result.Status == AudioGraphCreationStatus.Success)
                {
                    return result.Graph;
                }
            }
            catch(Exception ex) {
                Debug.WriteLine($"AudioHandler.AudioGraph.CreateAsync error:ex: Message:{ex.Message}, stacktrace:{ex.StackTrace}");
            }

            return null;
        }

        private async Task<AudioDeviceInputNode> CreateInputNodeAsync(AudioGraph graph)
        {
            try
            {
                var result = await graph.CreateDeviceInputNodeAsync(MediaCategory.Speech);
                Debug.WriteLine($"CreateInputNodeAsync, status:{result.Status}.");
                if (result.Status == AudioDeviceNodeCreationStatus.Success)
                {
                    return result.DeviceInputNode;
                }
                else
                {
                    return null;
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine($"CreateInputNodeAsync has an exception:{e}");
                return null;
            }
        }

        private AudioFrameOutputNode CreateFrameOutputNode()
        {
            AudioEncodingProperties encodingProperties = AudioEncodingProperties.CreatePcm(16000, 1, 16);
            return m_graph.CreateFrameOutputNode(encodingProperties);
        }
    }
}
